﻿/*
 * Description: Photoshop Process control ,to preload or show
 */

#include <iostream>
#include <locale>
#include <codecvt>
#include <tchar.h>
#include <cstring>
#include <windows.h>
#include <winuser.h>
#include <wingdi.h>
#include <tlhelp32.h>
#include <list>
#include <psapi.h>
#include <windef.h>
#include "processthreadsapi.h"
#pragma comment(lib, "User32.lib")
#pragma comment(lib, "winmm.lib")
#pragma comment(lib, "Kernel32.lib")
#pragma comment(lib, "FrameDyn.lib")
#include <shlwapi.h>
#include <algorithm>
#pragma comment(lib, "Shlwapi.lib")
namespace {
static const int SLEEP_TIME_LONG = 1000;  // 1000ms
static const int SLEEP_TIME_MED = 100;    // 100ms
static const int SLEEP_TIME_SHORT = 50;   // 50ms
static const int PS_COUNT = 30; // 计数，用于等待PS初始化完成以及hook窗口的等待过程
static const std::string ACTION_STARTPS = "startps";
static const std::string ACTION_SHOW = "show";
static const std::string ACTION_CAD_WINDOW = "cadwindow";
static const std::string ACTION_PS_WINDOW = "pswindow";
static const std::string PSHIDE = " -NoSplash";
static const std::string FILE_OPEN_ACTION = "open";
static const std::string PHOTOSHOP_WINDOW =  "GDI+ Window (Photoshop.exe)";
static const std::string CAD_SIGNIN_WINDOW = "活动见解";
static const int ARGV_INDEX_FIRST = 1;
static const int ARGV_INDEX_SECOND = 2;
static const int ARGV_INDEX_THREE = 3;
static const int ID_INVALID = 0xFFFFFFFF;
static const int CHAR_SIZE = 256;
const std::string PS_PRE_PROCESS_WNDCONTROL_EXE_PATH = "C:\\ProgramData\\performance\\ps_process_control.exe";
const std::string PHOTOSHOP_PRE = "Photoshop.exe";  // 后台PS进程名字

using _UnHookWndProc = BOOL (*)(void);
using _HookWndProc = int (*)(HINSTANCE hmod, DWORD dwThreadId);

#define WM_UNSUBCLASS (WM_USER + 1001)
struct ThreadProcessWindow {
    DWORD threadId = 0;
    DWORD dwProcessId = 0;
};

bool PreProcessFileExists(const std::string& filePath)
{
    WIN32_FILE_ATTRIBUTE_DATA attData = {0};
    bool ret = false;
    if (GetFileAttributesExA(filePath.c_str(), GetFileExInfoStandard, &attData) == TRUE) {
        ret = ((attData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0);
    }
    return ret;
}

bool PreProcessFilePatchCheck(const std::string &filePath)
{
    if (filePath.empty()) {
        return false;
    }
    if (!PreProcessFileExists(filePath)) {
        return false;
    }
    if ((filePath.find("..\\") != std::string::npos) || (filePath.find("../") != std::string::npos)) {
        return false;
    }
    int num = MultiByteToWideChar(CP_UTF8, 0, filePath.c_str(), -1, NULL, 0);
    if (num <= 0) {
        return false;
    }
    wchar_t *wide = new(std::nothrow) wchar_t[num];
    if (wide == nullptr) {
        return false;
    }
    MultiByteToWideChar(CP_UTF8, 0, filePath.c_str(), -1, wide, num);
    if (PathIsNetworkPathW(wide)) {
        delete[] wide;
        wide = nullptr;
        return false;
    }
    delete[] wide;
    wide = nullptr;
    return true;
}

void SuspendOrResumeProcessByThread(DWORD processId, bool isNeedSuspend)
{
    THREADENTRY32 th32 = {sizeof(THREADENTRY32)};
    HANDLE hThreadSanp = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, NULL);
    if (hThreadSanp == INVALID_HANDLE_VALUE) {
        return;
    }
    Thread32First(hThreadSanp, &th32);
    do {
        if (th32.th32OwnerProcessID != processId) {
            continue;
        }
        HANDLE hth = OpenThread(THREAD_ALL_ACCESS, FALSE, th32.th32ThreadID);
        if (hth == INVALID_HANDLE_VALUE) {
            continue;
        }
        if (isNeedSuspend) {
            SuspendThread(hth);
        } else {
            ResumeThread(hth);
        }
        CloseHandle(hth);
    } while (Thread32Next(hThreadSanp, &th32));

    if (hThreadSanp != INVALID_HANDLE_VALUE) {
        ::CloseHandle(hThreadSanp);
        hThreadSanp = INVALID_HANDLE_VALUE;
    }
    return;
}

bool SuspendOrResumeProcessByName(const std::string& processName, DWORD processId, bool isNeedSuspend)
{
    PROCESSENTRY32 pe32 = {sizeof(PROCESSENTRY32)};
    HANDLE hProcessSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (hProcessSnapShot == INVALID_HANDLE_VALUE || processId == 0) {
        return false;
    }

    Process32First(hProcessSnapShot, &pe32);
    do {
        std::string szExeFile = pe32.szExeFile;
        if (szExeFile == processName) {
            SuspendOrResumeProcessByThread(pe32.th32ProcessID, isNeedSuspend);
        }
    } while (Process32Next(hProcessSnapShot, &pe32));
    if (hProcessSnapShot != INVALID_HANDLE_VALUE) {
        ::CloseHandle(hProcessSnapShot);
        hProcessSnapShot = INVALID_HANDLE_VALUE;
    }
    return true;
}

bool SetProcessWorkingSet(DWORD processId)
{
    if (processId == 0) {
        return false;
    }
    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, false, processId);
    if (hProcess != INVALID_HANDLE_VALUE) {
        SetProcessWorkingSetSize(hProcess, -1, -1);
        ::CloseHandle(hProcess);
        hProcess = INVALID_HANDLE_VALUE;
    } else {
        return false;
    }
    return true;
}

BOOL CALLBACK EnumWindowUnclassAll(HWND hWnd, LPARAM lParam)
{
    if (lParam == NULL) {
        return FALSE;
    }
    ThreadProcessWindow *info = (ThreadProcessWindow *)lParam;
    DWORD dwProcessId = 0;
    DWORD threadId = GetWindowThreadProcessId(hWnd, &dwProcessId);
    // 判断是否是PS的窗口
    if (info->dwProcessId == dwProcessId && info->threadId == threadId) {
        SendMessage(hWnd, WM_UNSUBCLASS, 0, 0);
    }
    return TRUE;
}

void PsShow(std::string &filePath)
{
    HWND hWndShow = NULL;  //  ShowWindow
    hWndShow = FindWindowA(NULL, "Adobe Photoshop 2020");
    if (hWndShow) {
        ShowWindow(hWndShow, SW_SHOWNORMAL);
        ShowWindow(hWndShow, SW_MAXIMIZE);
        SetForegroundWindow(hWndShow);
        if (!filePath.empty() && PreProcessFilePatchCheck(filePath)) {
            LPCSTR strPath = filePath.c_str();
            LPCSTR fileAct = FILE_OPEN_ACTION.c_str();
            ShellExecuteA(hWndShow, fileAct, strPath, NULL, NULL, SW_SHOW);
        }
    }
    return;
}

bool UnHookQuit(PROCESS_INFORMATION pi, HMODULE hDll)
{
    _UnHookWndProc unHook = (_UnHookWndProc)GetProcAddress(hDll, "UnHookWndProc");
    if (unHook == NULL) {
        return false;
    }
    ThreadProcessWindow window;
    window.dwProcessId = pi.dwProcessId;
    window.threadId = pi.dwThreadId;
    EnumWindows(EnumWindowUnclassAll, (LPARAM)&window);  // 自定义窗口消息通过自定义的窗口过程卸载子类化的窗口
    auto retUn = unHook();                               // 移除hook成功则非0；
    if (retUn == 0) {
        return false;
    }
    return true;
}

bool StartPs(std::string &argvCmd)
{
    STARTUPINFO si;
    PROCESS_INFORMATION pi;
    ZeroMemory(&si, sizeof(STARTUPINFO));
    ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));
    si.cb = sizeof(STARTUPINFO);
    si.dwFlags = STARTF_USESHOWWINDOW;
    si.wShowWindow = SW_HIDE;
    std::string scmd = argvCmd + PSHIDE;
    HMODULE hDll = LoadLibrary("C:\\ProgramData\\performance\\ps_hide_window.dll");
    if (hDll == NULL) {
        return false;
    }
    _HookWndProc hookWnd = (_HookWndProc)GetProcAddress(hDll, "HookWndProc");
    if (hookWnd == nullptr) {
        FreeLibrary(hDll);
        return false;
    }

    // Step 1：拉起对应进程
    if (!CreateProcess(NULL, const_cast<char *>(scmd.c_str()),
        NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) {
        FreeLibrary(hDll);
        return false;
    }
    // Step 2：设置窗口消息Hook函数
    int ret = 0;
    int countStart = PS_COUNT;
    while (--countStart) {
        ret = hookWnd(hDll, pi.dwThreadId);
        if (ret == 0) {
            Sleep(SLEEP_TIME_SHORT);
        } else {
            break;
        }
    }

    if (ret > 0) {
        // Step 3：等待应用初始化完成
        Sleep(PS_COUNT * SLEEP_TIME_LONG);
        // Step 4：解除子类化 + unHook（可以选择不unhook，在自定义消息中解除隐藏屏蔽也可以）
        UnHookQuit(pi, hDll);
        // Step 5：挂起PS进程 + 压缩内存
        SuspendOrResumeProcessByName(PHOTOSHOP_PRE, pi.dwProcessId, true);
        Sleep(SLEEP_TIME_MED);
        SetProcessWorkingSet(pi.dwProcessId);
    }
    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);
    FreeLibrary(hDll);  // 释放加载的动态链接库 (DLL) 模块
}
BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam)
{
    char windowText[CHAR_SIZE] = {0};
    GetWindowTextA(hwnd, windowText, sizeof(windowText));
    std::string windowTitle(windowText);
    const char* lParamstr = reinterpret_cast<char*>(lParam);
    std::string lParamStr(lParamstr);
    if (windowTitle == lParamStr) {
        if (lParamStr == CAD_SIGNIN_WINDOW) {
            PostMessage(hwnd, WM_CLOSE, 0, 0);
        } else if (lParamStr == PHOTOSHOP_WINDOW) {
            return FALSE;  // 返回FALSE以停止枚举
        }
    }
    return TRUE;
}
}  // namespace
int main(int argc, char **argv)
{
    if (argc < ARGV_INDEX_SECOND) {
        return 0;
    }
    std::string argvAction = argv[ARGV_INDEX_FIRST];
    std::string argvCmd = argc > ARGV_INDEX_SECOND ? argv[ARGV_INDEX_SECOND] : "";
    std::string argvCmdLine = argc > ARGV_INDEX_THREE ? argv[ARGV_INDEX_THREE] : "";
    if (argvAction == ACTION_STARTPS) {
        if (PreProcessFilePatchCheck(argvCmd)) {
            StartPs(argvCmd);
        }
    } else if (argvAction == ACTION_SHOW) {
        SuspendOrResumeProcessByName(PHOTOSHOP_PRE, ID_INVALID, false);
        if (PreProcessFilePatchCheck(argvCmd)) {
            PsShow(argvCmdLine);
        }
    } else if (argvAction == ACTION_PS_WINDOW || argvAction == ACTION_CAD_WINDOW) {
        return EnumWindows(EnumWindowsProc, reinterpret_cast<LPARAM>(argvAction == ACTION_PS_WINDOW ?
            PHOTOSHOP_WINDOW.c_str() : CAD_SIGNIN_WINDOW.c_str()));
    }
    return 0;
}